﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Xml.Linq;
using GuiLabs.Utils.Actions;

namespace DynamicGeometry
{
    public class Drawing
    {
        public Drawing(Canvas newCanvas)
        {
            ActionManager = new ActionManager();
            Behavior = new Dragger();

            Figures = new RootFigureList() { Drawing = this };

            OnAttachToCanvas += Drawing_OnAttachToCanvas;
            OnDetachFromCanvas += Drawing_OnDetachFromCanvas;
            Canvas = newCanvas;

            CoordinateSystem = new CoordinateSystem(this);
            CoordinateGrid = new CartesianGrid() { Drawing = this };
            Figures.Add(CoordinateGrid);
        }

        public IFigure this[string index]
        {
            get { return Figures[index]; }
        }

        void Drawing_OnAttachToCanvas(Canvas e)
        {
            e.SizeChanged += mCanvas_SizeChanged;
            Figures.OnAddingToCanvas(e);
        }

        void Drawing_OnDetachFromCanvas(Canvas e)
        {
            e.SizeChanged -= mCanvas_SizeChanged;
            Figures.OnRemovingFromCanvas(e);
        }

        #region Events

        public event EventHandler<SelectionChangedEventArgs> SelectionChanged;

        public class SelectionChangedEventArgs : EventArgs
        {
            public SelectionChangedEventArgs()
            {
                SelectedFigures = Enumerable.Empty<IFigure>();
            }

            public SelectionChangedEventArgs(IEnumerable<IFigure> selection)
                : this()
            {
                SelectedFigures = selection;
            }

            public SelectionChangedEventArgs(IFigure singleSelection)
                : this(singleSelection.AsEnumerable())
            {
            }

            public IEnumerable<IFigure> SelectedFigures { get; set; }
        }

        internal void ClearSelectedFigures()
        {
            foreach (IFigure figure in this.Figures)
            {
                if (figure.Selected)
                {
                    figure.Selected = false;
                }
            }
        }

        internal void RaiseSelectionChanged(SelectionChangedEventArgs args)
        {
            if (SelectionChanged != null)
            {
                SelectionChanged(this, args);
            }
        }

        public class ConstructionStepCompleteEventArgs : EventArgs
        {
            public bool ConstructionComplete { get; set; }
            public Type FigureTypeNeeded { get; set; }
        }

        public event EventHandler<ConstructionStepCompleteEventArgs> ConstructionStepComplete;

        internal void RaiseConstructionStepComplete(ConstructionStepCompleteEventArgs args)
        {
            if (ConstructionStepComplete != null)
            {
                ConstructionStepComplete(this, args);
            }
        }

        public class ConstructionFeedbackEventArgs : EventArgs
        {
            public Type FigureTypeNeeded { get; set; }
            public bool IsMouseButtonDown { get; set; }
        }

        public event EventHandler<ConstructionFeedbackEventArgs> ConstructionFeedback;
        internal void RaiseConstructionFeedback(ConstructionFeedbackEventArgs args)
        {
            if (ConstructionFeedback != null)
            {
                ConstructionFeedback(this, args);
            }
        }

        public class DocumentOpenRequestedEventArgs : EventArgs
        {
            public enum InWhichWindowChoice
            {
                DontCare,
                ReuseCurrent,
                NewWindowOrTab
            }

            public string DocumentXml { get; set; }
            public InWhichWindowChoice InWhichWindow { get; set; }
        }

        /// <summary>
        /// This event is raised when the user clicks on a hyperlink
        /// to open another drawing document, much like a web-browser link.
        /// This event signals to the host of the drawing to either open this 
        /// new drawing in a separate tab or replace the current one.
        /// </summary>
        public event EventHandler<DocumentOpenRequestedEventArgs> DocumentOpenRequested;
        internal void RaiseDocumentOpenRequested(DocumentOpenRequestedEventArgs args)
        {
            if (DocumentOpenRequested != null)
            {
                DocumentOpenRequested(this, args);
            }
        }

        public event SizeChangedEventHandler SizeChanged;
        internal void RaiseSizeChanged(SizeChangedEventArgs args)
        {
            if (SizeChanged != null)
            {
                SizeChanged(this, args);
            }
        }

        public event Action<string> Status;

        internal void RaiseStatusNotification(string status)
        {
            if (Status != null)
            {
                Status(status);
            }
        }

        #endregion

        public string Name { get; set; }
        public override string ToString()
        {
            return Name;
        }

        public ActionManager ActionManager { get; private set; }

        public event Action<Canvas> OnAttachToCanvas;
        public event Action<Canvas> OnDetachFromCanvas;

        public event Action<Behavior> BehaviorChanged;

        private Canvas mCanvas;
        public Canvas Canvas
        {
            get
            {
                return mCanvas;
            }
            set
            {
                if (mCanvas != null && OnDetachFromCanvas != null)
                {
                    OnDetachFromCanvas(mCanvas);
                }
                mCanvas = value;
                if (mCanvas != null && OnAttachToCanvas != null)
                {
                    OnAttachToCanvas(mCanvas);
                }
            }
        }

        void mCanvas_SizeChanged(object sender, System.Windows.SizeChangedEventArgs e)
        {
            RaiseSizeChanged(e);
            Recalculate();
        }

        private Behavior mBehavior;
        public Behavior Behavior
        {
            get
            {
                return mBehavior;
            }
            set
            {
                if (mBehavior == value)
                {
                    return;
                }
                if (mBehavior != null)
                {
                    mBehavior.Drawing = null;
                }
                mBehavior = value;
                if (mBehavior != null)
                {
                    mBehavior.Drawing = this;
                }
                if (BehaviorChanged != null)
                {
                    BehaviorChanged(value);
                }
            }
        }

        public void SetDefaultBehavior()
        {
            Behavior = Behavior.Singletons.FirstOrDefault(b => b is Dragger);
        }

        public CoordinateSystem CoordinateSystem { get; set; }
        public CartesianGrid CoordinateGrid { get; set; }

        public FigureList Figures { get; set; }

        public IEnumerable<IFigure> GetSerializableFigures()
        {
            // first points
            foreach (IFigure figure in Figures)
            {
                if (figure is IPoint)
                {
                    yield return figure;
                }
            }

            // not point and not CartesianGrid
            foreach (IFigure figure in Figures)
            {
                if ((!(figure is IPoint)) && (!(figure is CartesianGrid)))
                {
                    yield return figure;
                }
            }
        }

        public IEnumerable<IFigure> GetSelectedFigures()
        {
            foreach (IFigure figure in Figures)
            {
                if (figure.Selected)
                {
                    yield return figure;
                }
            }
        }

#if !SILVERLIGHT
        public static Drawing Load(string path, Canvas canvas)
        {
            Drawing drawing = new Drawing(canvas);
            DrawingDeserializer.ReadDrawing(drawing, System.IO.File.ReadAllText(path));
            return drawing;
        }

        public void Save(string path)
        {
            DrawingSerializer.Save(this, path);
        }
#endif

        public void Add(IFigure figure)
        {
            AddFigureAction action = new AddFigureAction(this, figure);
            ActionManager.RecordAction(action);
        }

        public void Remove(IFigure figure)
        {
            RemoveFigureAction action = new RemoveFigureAction(this, figure);
            ActionManager.RecordAction(action);
        }

        public void Recalculate()
        {
            Figures.Recalculate();
        }

        public void Add(System.Collections.Generic.IEnumerable<IFigure> figures)
        {
            using (Transaction.Create(ActionManager, true))
                figures.ForEach(Add);
        }

        public void Add(params IFigure[] figures)
        {
            Add((IEnumerable<IFigure>)figures);
        }

        public void AddFromXml(XElement element)
        {
            DrawingDeserializer.ReadDrawing(this, element);
        }

        public string SaveAsText()
        {
            return DrawingSerializer.SaveDrawing(this);
        }

        public void Delete()
        {
            var action = new RemoveFiguresAction(
                this,
                this.GetSelectedFigures().TopologicalSort(f => f.Dependents));
            ActionManager.RecordAction(action);
        }

        public void SelectAll()
        {
            foreach (IFigure figure in Figures)
            {
                if ((!(figure is IPoint)) && (!(figure is CartesianGrid)))
                {
                    figure.Selected = true;
                }
            }
        }
    }
}
